﻿using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using DCT.ViewModel;
using System.Collections.Generic;

namespace DCT.View
{
    public class DragDropHelper
    {
        #region Statics

        public static double VerFix = 6;
        public static double HorFix = 6;

        #endregion

        #region Fields

        private DataFormat format = DataFormats.GetDataFormat("DragDropItemsControl");
        private Point initialMousePosition;
        private object draggedData;
        private FrameworkElement draggedElement;
        private DraggedAdorner draggedAdorner;
        private Drawing dragDropDrawing;
        private Window topWindow;
        private ItemsControl sourceItemsControl;
        private FrameworkElement sourceItemContainer;
        private ItemsControl targetItemsControl;
        private FrameworkElement targetItemContainer;
        private FrameworkElement targetCanvas;
        private bool dropAllow;

        // singleton
        private static DragDropHelper _instance;
        private static DragDropHelper Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new DragDropHelper();
                }
                return _instance;
            }
        }

        #endregion

        #region GetDragDropTemplate

        public static DataTemplate GetDragDropTemplate(DependencyObject obj)
        {
            return (DataTemplate)obj.GetValue(DragDropTemplateProperty);
        }

        public static void SetDragDropTemplate(DependencyObject obj, DataTemplate value)
        {
            obj.SetValue(DragDropTemplateProperty, value);
        }

        public static readonly DependencyProperty DragDropTemplateProperty =
            DependencyProperty.RegisterAttached("DragDropTemplate", typeof(DataTemplate), typeof(DragDropHelper), new UIPropertyMetadata(null));

        #endregion

        #region IsDragSource

        public static bool GetIsDragSource(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsDragSourceProperty);
        }

        public static void SetIsDragSource(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDragSourceProperty, value);
        }

        public static readonly DependencyProperty IsDragSourceProperty =
            DependencyProperty.RegisterAttached("IsDragSource", typeof(bool), typeof(DragDropHelper), new UIPropertyMetadata(false, DragSourceChanged));

        private static void DragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var dragSource = obj as ItemsControl;
            if (dragSource != null)
            {
                if (Object.Equals(e.NewValue, true))
                {
                    dragSource.PreviewMouseLeftButtonDown += Instance.DragSource_PreviewMouseLeftButtonDown;
                    dragSource.PreviewMouseLeftButtonUp += Instance.DragSource_PreviewMouseLeftButtonUp;
                    dragSource.PreviewMouseMove += Instance.DragSource_PreviewMouseMove;
                }
                else
                {
                    dragSource.PreviewMouseLeftButtonDown -= Instance.DragSource_PreviewMouseLeftButtonDown;
                    dragSource.PreviewMouseLeftButtonUp -= Instance.DragSource_PreviewMouseLeftButtonUp;
                    dragSource.PreviewMouseMove -= Instance.DragSource_PreviewMouseMove;
                }
            }
        }

        #endregion

        #region IsDropTarget

        public static bool GetIsDropTarget(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsDropTargetProperty);
        }

        public static void SetIsDropTarget(DependencyObject obj, bool value)
        {
            obj.SetValue(IsDropTargetProperty, value);
        }

        public static readonly DependencyProperty IsDropTargetProperty =
            DependencyProperty.RegisterAttached("IsDropTarget", typeof(bool), typeof(DragDropHelper), new UIPropertyMetadata(false, DropTargetChanged));

        private static void DropTargetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var dropTarget = obj as ItemsControl;
            if (dropTarget != null)
            {
                if (Object.Equals(e.NewValue, true))
                {
                    dropTarget.AllowDrop = true;
                    dropTarget.PreviewDrop += Instance.DropTarget_PreviewDrop;
                    dropTarget.PreviewDragEnter += Instance.DropTarget_PreviewDragEnter;
                    dropTarget.PreviewDragOver += Instance.DropTarget_PreviewDragOver;
                }
                else
                {
                    dropTarget.AllowDrop = false;
                    dropTarget.PreviewDrop -= Instance.DropTarget_PreviewDrop;
                    dropTarget.PreviewDragEnter -= Instance.DropTarget_PreviewDragEnter;
                    dropTarget.PreviewDragOver -= Instance.DropTarget_PreviewDragOver;
                }
            }
        }

        #endregion

        #region DragSource

        private void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.sourceItemsControl = (ItemsControl)sender;
            Visual visual = e.OriginalSource as Visual;

            this.topWindow = (Window)FindAncestor(typeof(Window), this.sourceItemsControl);
            this.initialMousePosition = e.GetPosition(this.topWindow);

            this.sourceItemContainer = GetItemContainer(this.sourceItemsControl, visual);
            if (this.sourceItemContainer != null)
            {
                this.draggedData = this.sourceItemContainer.DataContext;
                this.draggedElement = VisualTreeHelper.GetChild(sourceItemContainer, 0) as FrameworkElement;

                if (this.draggedElement != null)
                {
                    DependencyObject element = this.draggedElement;
                    while (element != null && !(element is DrawingVisual))
                    {
                        element = VisualTreeHelper.GetChild(element, 0);
                    }

                    if (element != null)
                    {
                        dragDropDrawing = (element as DrawingVisual).Drawing;
                    }
                }
            }
        }

        private void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (this.draggedData != null)
            {
                // Only drag when user moved the mouse by a reasonable amount.
                if (IsMovementBigEnough(this.initialMousePosition, e.GetPosition(this.topWindow)))
                {
                    DataObject data = new DataObject(this.format.Name, this.draggedData);

                    // Adding events to the window to make sure dragged adorner comes up when mouse is not over a drop target.
                    bool previousAllowDrop = this.topWindow.AllowDrop;
                    this.topWindow.AllowDrop = true;
                    this.topWindow.DragEnter += TopWindow_DragEnter;
                    this.topWindow.DragOver += TopWindow_DragOver;
                    this.topWindow.DragLeave += TopWindow_DragLeave;

                    dropAllow = true;
                    System.Windows.DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);

                    // Without this call, there would be a bug in the following scenario: Click on a data item, and drag
                    // the mouse very fast outside of the window. When doing this really fast, for some reason I don't get 
                    // the Window leave event, and the dragged adorner is left behind.
                    // With this call, the dragged adorner will disappear when we release the mouse outside of the window,
                    // which is when the DoDragDrop synchronous method returns.
                    RemoveDraggedAdorner();

                    this.topWindow.AllowDrop = previousAllowDrop;
                    this.topWindow.DragEnter -= TopWindow_DragEnter;
                    this.topWindow.DragOver -= TopWindow_DragOver;
                    this.topWindow.DragLeave -= TopWindow_DragLeave;

                    this.draggedData = null;
                }
            }
        }

        private void DragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.draggedData = null;
        }

        #endregion

        #region DropTarget

        private void DropTarget_PreviewDragEnter(object sender, DragEventArgs e)
        {
            this.targetItemsControl = (ItemsControl)sender;
            object draggedItem = e.Data.GetData(this.format.Name);

            DecideDropTarget(e);
            if (draggedItem != null)
            {
                //创建
                ShowDraggedAdorner(e.GetPosition(this.topWindow));
            }
            e.Handled = true;
        }

        private void DropTarget_PreviewDragOver(object sender, DragEventArgs e)
        {
            object draggedItem = e.Data.GetData(this.format.Name);

            DecideDropTarget(e);
            if (draggedItem != null)
            {
                //更新
                ShowDraggedAdorner(e.GetPosition(this.topWindow));
            }
            e.Handled = true;
        }

        private void DropTarget_PreviewDrop(object sender, DragEventArgs e)
        {
            DeviceBaseViewModel draggedItem = e.Data.GetData(this.format.Name) as DeviceBaseViewModel;
            if (draggedItem != null && dropAllow)
            {
                dropAllow = false;

                e.GetPosition(draggedAdorner);
                //要修正创建Adorner时为线宽留余量所造成的误差
                Point dropPoint = new Point(e.GetPosition(targetCanvas).X - e.GetPosition(draggedAdorner).X + draggedItem.RenderTransformOrigin.X * draggedItem.DeviceWidth + HorFix / 2,
                                                        e.GetPosition(targetCanvas).Y - e.GetPosition(draggedAdorner).Y + draggedItem.RenderTransformOrigin.Y * draggedItem.DeviceHeight + VerFix / 2);
                
                //序列化后反序列化
                draggedItem = Utilities.DeserializeDeviceViewModels("DSU", Utilities.SerializeDeviceViewModels("DSU", new List<DeviceBaseViewModel>() { draggedItem }), dropPoint)[0];

                DeviceCanvas deviceCanvas = targetCanvas as DeviceCanvas;
                DeviceMapViewModel deviceMapViewModel = deviceCanvas.DataContext as DeviceMapViewModel;

                if (deviceMapViewModel.DeviceRepository.Add(draggedItem))
                {
                    deviceMapViewModel.UndoRedo.InsertInUnDoRedoForInsert(draggedItem);
                }
            }
            RemoveDraggedAdorner();
            e.Handled = true;
        }

        #endregion

        #region TopWindow

        private void TopWindow_DragEnter(object sender, DragEventArgs e)
        {
            ShowDraggedAdorner(e.GetPosition(this.topWindow));
            e.Effects = DragDropEffects.None;
            e.Handled = true;
        }

        private void TopWindow_DragOver(object sender, DragEventArgs e)
        {
            ShowDraggedAdorner(e.GetPosition(this.topWindow));
            e.Effects = DragDropEffects.None;
            e.Handled = true;
        }

        private void TopWindow_DragLeave(object sender, DragEventArgs e)
        {
            RemoveDraggedAdorner();
            e.Handled = true;
        }

        #endregion

        #region Adorners

        // 创建或者更新Adorner 
        private void ShowDraggedAdorner(Point currentPosition)
        {
            if (this.draggedAdorner == null)
            {
                var adornerLayer = AdornerLayer.GetAdornerLayer(this.sourceItemsControl);
                this.draggedAdorner = new DraggedAdorner(dragDropDrawing, GetDragDropTemplate(this.sourceItemsControl), draggedElement, adornerLayer);
            }
            this.draggedAdorner.SetPosition(currentPosition.X - this.initialMousePosition.X, currentPosition.Y - this.initialMousePosition.Y);
        }

        private void RemoveDraggedAdorner()
        {
            if (this.draggedAdorner != null)
            {
                this.draggedAdorner.Detach();
                this.draggedAdorner = null;
            }
        }

        #endregion

        #region Utilities

        private void DecideDropTarget(DragEventArgs e)
        {
            object draggedItem = e.Data.GetData(this.format.Name);

            if (IsDropDataTypeAllowed(draggedItem))
            {
                this.targetItemContainer = GetItemContainer(this.targetItemsControl, e.OriginalSource as Visual);
                this.targetCanvas = FindAncestor(typeof(Canvas), e.OriginalSource as Visual);

                if (this.targetCanvas == null || this.targetItemContainer != null)
                {
                    e.Effects = DragDropEffects.None;
                }
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }

        private bool IsDropDataTypeAllowed(object draggedItem)
        {
            bool isDropDataTypeAllowed;
            IEnumerable collectionSource = this.targetItemsControl.ItemsSource;
            if (draggedItem != null)
            {
                if (collectionSource != null)
                {
                    Type draggedType = draggedItem.GetType();
                    Type collectionType = collectionSource.GetType();

                    Type genericIListType = collectionType.GetInterface("IList`1");
                    if (genericIListType != null)
                    {
                        Type[] genericArguments = genericIListType.GetGenericArguments();
                        isDropDataTypeAllowed = genericArguments[0].IsAssignableFrom(draggedType);
                    }
                    else if (typeof(IList).IsAssignableFrom(collectionType))
                    {
                        isDropDataTypeAllowed = true;
                    }
                    else
                    {
                        isDropDataTypeAllowed = false;
                    }
                }
                else // the ItemsControl's ItemsSource is not data bound.
                {
                    isDropDataTypeAllowed = true;
                }
            }
            else
            {
                isDropDataTypeAllowed = false;
            }
            return isDropDataTypeAllowed;
        }

        public static FrameworkElement GetItemContainer(ItemsControl itemsControl, Visual bottomMostVisual)
        {
            FrameworkElement itemContainer = null;
            if (itemsControl != null && bottomMostVisual != null && itemsControl.Items.Count >= 1)
            {
                var firstContainer = itemsControl.ItemContainerGenerator.ContainerFromIndex(0);
                if (firstContainer != null)
                {
                    Type containerType = firstContainer.GetType();

                    itemContainer = FindAncestor(containerType, bottomMostVisual);

                    if (itemContainer != null && itemContainer.DataContext != null)
                    {
                        FrameworkElement itemContainerVerify = itemsControl.ItemContainerGenerator.ContainerFromItem(itemContainer.DataContext) as FrameworkElement;
                        if (itemContainer != itemContainerVerify)
                        {
                            itemContainer = null;
                        }
                    }
                }
            }
            return itemContainer;
        }

        public static FrameworkElement FindAncestor(Type ancestorType, Visual visual)
        {
            while (visual != null && !ancestorType.IsInstanceOfType(visual))
            {
                visual = (Visual)VisualTreeHelper.GetParent(visual);
            }
            return visual as FrameworkElement;
        }

        public static bool IsMovementBigEnough(Point initialMousePosition, Point currentPosition)
        {
            return (Math.Abs(currentPosition.X - initialMousePosition.X) >= SystemParameters.MinimumHorizontalDragDistance ||
                 Math.Abs(currentPosition.Y - initialMousePosition.Y) >= SystemParameters.MinimumVerticalDragDistance);
        }

        #endregion
    }
}
